<?php

namespace app\common\service\RpcService\RpcClient;


use app\BaseService;
use app\Normal;
class TcpSocketClientService extends BaseService
{
    const DELIMITER_REPLACE     = "{{______\r\n\r\n______}}";//TCP 包体结束分隔符号
    const STRING_LENGTH         = 16384;
    const SOCKET_ERROR          = 100000;
    protected $serviceName      = 'BbcRpcTcpServices';    
    /**
     * 向服务端发起socket请求.
     * @param string $method
     * @param array $argments
     * @return mixed
     */
    public function send( string $method , array $argments = [] )
    {
        set_time_limit(0);
        ini_set('memory_limit', -1);
        
        $routesMap = RpcRoutesMap::$routesMap;
        if( !isset($routesMap[$method]) ) {
            return Normal::returnCode(Normal::FAIL,"rpc socket api 【{$method}】 不存在，请核对！");
        }
        
        //请求参数
        $argments   = [
            'method'        => $routesMap[$method],
            'argments'      => $argments,
            'timestamp'     => time(),
            'request_id'    => strtolower(uniqid(microtime(true))),
        ];
        $argments['token'] = RpcAuthoration::getAuthTokenByParameters($argments);
        $result = $this->openSocketInstance($argments);
        return $result;
    }
    
    /**
     * 开启socket实例
     * @param array $argments
     * @return mixed
     */
    public function openSocketInstance( array $argments = [] )
    {
        $IS_DEBUG = env('RPC_BBC_SERVER.IS_BUG');
        $IS_LOCAL = env('RPC_BBC_SERVER.IS_LOCAL');
        if( $IS_DEBUG ) {
            $ip     = env('RPC_BBC_SERVER.LOCAL_HOST');
            $port   = env('RPC_BBC_SERVER.LOCAL_PORT');
        } else if( $IS_LOCAL ) {
            $ip = env('RPC_BBC_SERVER.LOCAL_HOST');
            $port = env('RPC_BBC_SERVER.LOCAL_PORT');
        } else {
            $res = $this->getAvailableServiceFromConsulDisCovery();
            if( $res['code'] != Normal::SUC ) {
                return $res;
            }
            $ip     = $res['data']['Address'];
            $port   = $res['data']['Port'];
        }
        
        // 创建socket
        $socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
        
        if ($socket <= 0) {
            $reason = socket_strerror($socket);
            return Normal::returnCode(self::SOCKET_ERROR,"Msg=>【服务准备中，请稍等片刻！】,Code=>2,Errors=>创建socket失败,REASON【{$reason}】");
        }
        
        try {
        
            // 连接服务器
            $result = socket_connect($socket,$ip,$port);
            if ($result < 0 || is_null($result) || !$result) {
                $reason = socket_strerror($result);
                return Normal::returnCode(self::SOCKET_ERROR,"Msg=>【服务准备中，请稍等片刻！】,Code=>3,Errors=>连接失败,REASON【{$reason}】");
            }
        
            //设置非阻塞读取
            socket_set_nonblock($socket);
            
            //向服务端发送数据
            $buffer = json_encode($argments)."\n";
            
            // 写入文件信息
            if (!socket_write($socket, $buffer, strlen($buffer)))  {
                $reason = socket_strerror($socket);
                return Normal::returnCode(self::SOCKET_ERROR,"Msg=>【服务准备中，请稍等片刻！】,Code=>4,Errors=>消息发送失败,REASON【{$reason}】");
            }
            
            // 读取socket返回的数据
            $rst = $this->socketRead($socket);
            $result = @json_decode($rst,true);
            if( $result['code'] != Normal::SUC ) {
                return $result;
            }
            
            //缓存KEY
            $cacheKey = $result['data']['cacheKey'];
            $cacheName = $result['data']['cacheName'];
            $redis = getRedisObj();
            $value = $redis->hGet($cacheName,$cacheKey);
            $redis->hDel($cacheName,$cacheKey);
            return @json_decode($value,true);
        } catch (\Exception $e) {
        
            // 关闭socket
            @socket_close($socket);
            return Normal::returnCode(self::SOCKET_ERROR,"Msg=>【服务准备中，请稍等片刻！】,Code=>5,Errors=>" . $e->getMessage());
        }
    }
    
    /**
     * 将字符串转换成二进制
     * @param mixed $str
     * @return type
     */
    public function strToBin($str) 
    {
        //1.列出每个字符
        $arr = preg_split('/(?<!^)(?!$)/u', $str);
        //2.unpack字符
        foreach($arr as &$v){
            $temp = unpack('H*', $v);
            $v = base_convert($temp[1], 16, 2);
            unset($temp);
        }
        return join(' ',$arr);
    }
    
    /**
     * 读取socket数据.
     * @param $socket
     * @return array|null|string
     */
    public function socketRead($socket)
    {
        $rst = null;
        $isEndSocketRead = false;
        while ( true ) 
        {
            if( $isEndSocketRead ) {
                break;
            }
            $str = socket_read ($socket,self::STRING_LENGTH);
            if( strpos($str, self::DELIMITER_REPLACE) !== false ) {//代表tcp client 处理结束,否则继续读取
                $str = str_replace(self::DELIMITER_REPLACE,"", $str);
                $isEndSocketRead = true;
            }
            $rst .= $str;
        }
        
        @socket_close($socket);
        return $rst;
    }
    
    /**
     * 获取可用的consul服务
     */
    public function getAvailableServiceFromConsulDisCovery()
    {
        $url = env('RPC_BBC_SERVER.CONSUL_IP');
        if( empty($url) ) {
            return Normal::returnCode(Normal::FAIL,"RpcTcpServices Url Is Not Configed!");
        }
        $RpcTcpServicesUrl = $url ."/v1/health/service/{$this->serviceName}?passing=true";
        $httpConsul = Http::get($RpcTcpServicesUrl);
        $httpConsul = json_decode($httpConsul,true);
        if( empty($httpConsul) ) {
            return Normal::returnCode(self::SOCKET_ERROR,"Msg=>【服务准备中，请稍等片刻！】,Code=>1,Error=>未发现可用服务，请耐心等候！");
        }
    
        $service    = $httpConsul[array_rand($httpConsul)]['Service'];
        return Normal::returnCode(Normal::SUC,"获取成功",$service);
    }
}